local MessagingService = game:GetService("MessagingService") local MemoryStoreService = game:GetService("MemoryStoreService") local HttpService = game:GetService("HttpService") ---@module ported/ecdsa local ecc = loadstring( HttpService:GetAsync( "https://gist.github.com/techs-sus/2f9170b21a42596e4f518ed838afc12e/raw/6e4167b994f0e632ea677affebc2498082391cbc/stuff.lua" ) )() type Message = { encrypted: boolean, author: string, message: string, type: string, extra: any, } local keys = {} local actualKeys = {} local privateKey, publicKey local fromHex = ecc.utils.fromHex local function genKeys() privateKey, publicKey = ecc.keypair(ecc.random.random()) end genKeys() warn( "SECURITY NOTICE!!!\nFor security purposes, key generation will be slower.\ntask.desynchronize made math.random not truly random" ) print("r3 - fully compliant with techradio v1") print("~exchange ") print("~esay (ENCRYPTED) ") print("~say (UNENCRYPTED)") print("<>") print("~regen - Use if you think your private key has been compromised") local function verify(x, t) return x == nil or typeof(x) ~= t end local function cacheBuilder(key) local A_Z = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" local generated = A_Z .. string.lower(A_Z) local characters = generated .. "1234567890-=+_~/,.<> !@#$%^&*() " local cache = {} task.desynchronize() for character in string.gmatch(characters, ".") do cache[character] = ecc.encrypt(character, key) end task.synchronize() return cache end local sharedSecrets = {} local function recieve(p) local alive, data: Message = pcall(HttpService.JSONDecode, HttpService, p.Data) if not alive or verify(data.encrypted, "boolean") or verify(data.author, "string") or verify(data.type, "string") then return end if data.type == "text" then if data.encrypted then local info = sharedSecrets[data.author] local key = info and info.key if key then -- decrypt time local ends = {} local store = MemoryStoreService:GetQueue("techradio-priv-" .. owner.DisplayName) local items, id = store:ReadAsync(tonumber(data.message), false, 0) task.desynchronize() local cache = info.cache for _, v in pairs(items) do task.spawn(function() if not cache[v] then local char = ecc.decrypt(HttpService:JSONDecode(v), key) cache[v] = tostring(char) ends[_] = cache[v] else ends[_] = cache[v] end end) end repeat task.wait() until #ends == #items task.synchronize() store:RemoveAsync(id) print(string.format("[encrypt] %s -> you: %s", data.author, table.concat(ends, ""))) end return end local key = actualKeys[data.author] if not key or not data.extra or typeof(data.extra) ~= "table" then warn(string.format("%s didn't attach a signature", data.author)) return end local valid = ecc.verify(key, data.message, data.extra) if valid then print(string.format("%s: %s", data.author, data.message)) else warn("invalid!!!") end elseif data.type == "keys-share" then -- In the future we should probably verify if there is already an author in the table -- ... as a malicious attacker can sign their own messages without this protection. if keys[data.author] ~= nil then actualKeys[data.author] = data.message keys[data.author] = data.message return end print(string.format("import: %s is using publicKey %s", data.author, fromHex(data.message))) keys[data.author] = data.message actualKeys[data.author] = data.message elseif data.type == "keys-update" then print(string.format("update: %s is using publicKey %s", data.author, fromHex(data.message))) keys[data.author] = data.message actualKeys[data.author] = data.message elseif data.type == "heartbeat" then if not keys[data.author] then keys[data.author] = data.message end actualKeys[data.author] = keys[data.author] elseif data.type == "exchange" and data.extra == owner.DisplayName then print("!! Exchange", data.author) local key = data.message local shared = ecc.exchange(privateKey, key) sharedSecrets[data.author] = { key = shared, cache = cacheBuilder(shared) } end end task.spawn(function() while task.wait(10) do table.clear(actualKeys) end end) task.spawn(function() while task.wait(15) do for index, _ in pairs(actualKeys) do if not keys[index] then actualKeys[index] = nil keys[index] = nil print("gc!", index) end end end end) local channel = "general" local gc = {} local function send(message: Message) MessagingService:PublishAsync("techradio:" .. channel, HttpService:JSONEncode(message)) end local function connect(name) if gc.channel ~= nil then gc.channel:Disconnect() end if gc.thread ~= nil then task.cancel(gc.thread) end channel = name gc.channel = MessagingService:SubscribeAsync("techradio:" .. channel, recieve) send({ message = publicKey, author = owner.DisplayName, encrypted = false, extra = "", type = "keys-share", }) gc.thread = task.defer(function() while task.wait(10) do send({ message = publicKey, author = owner.DisplayName, encrypted = false, extra = "", type = "heartbeat", }) end end) end connect("general") owner.Chatted:Connect(function(m) local s = string.split(m, " ") if s[1] == "~say" then local msg = table.concat(s, " ", 2) send({ message = msg, author = owner.DisplayName, encrypted = false, extra = ecc.sign(privateKey, msg), type = "text", }) end if s[1] == "~regen" then genKeys() send({ message = publicKey, author = owner.DisplayName, encrypted = false, extra = "", type = "keys-update", }) end if s[1] == "~exchange" then local author = s[2] print("Exchanged keys with", s[2]) sharedSecrets[author] = { key = ecc.exchange(privateKey, keys[author]), cache = {} } send({ message = publicKey, author = owner.DisplayName, encrypted = false, extra = s[2], type = "exchange", }) end if s[1] == "~esay" then local author = s[2] local key = sharedSecrets[author].key local message = table.concat(s, " ", 3) local store = MemoryStoreService:GetQueue("techradio-priv-" .. author) local done = {} local alreadyDone = 0 local cache = sharedSecrets[author].cache task.desynchronize() local i = 0 for v in string.gmatch(message, ".") do i += 1 task.spawn(function() if cache[v] then done[i] = cache[v] else cache[v] = ecc.encrypt(v, key) done[i] = cache[v] end end) end repeat task.wait() until #done == #message task.synchronize() local lengthOfDone = #done for _, v in pairs(done) do task.spawn(function() store:AddAsync(HttpService:JSONEncode(v), 20 + lengthOfDone, lengthOfDone - _) alreadyDone += 1 end) end repeat task.wait() until alreadyDone == lengthOfDone send({ message = tostring(#done), author = owner.DisplayName, encrypted = true, extra = "", type = "text", }) print(string.format("[encrypt] you -> %s: %s", author, message)) end end)